Skip to content

Implement embedded build for cockpit with host app integration - #3

Merged
danieljtrujillo merged 2 commits into
mainfrom
cockpit
Aug 30, 2026
Merged

Implement embedded build for cockpit with host app integration#3
danieljtrujillo merged 2 commits into
mainfrom
cockpit

Conversation

@danieljtrujillo

Copy link
Copy Markdown
Collaborator

This pull request adds a new LICENSE file to the repository. The license used is the MIT License, which allows for broad use, modification, and distribution of the software with minimal restrictions. The file also includes a note about third-party code and attribution.

Licensing:

  • Added a LICENSE file with the MIT License, granting permission for use, modification, and distribution, and including a notice about third-party code and attributions.

A second build target, dist-embed/, serves the same cockpit from a host
application (theDAW's SWAY tab) while the desktop app stays untouched.
`npm run build:renderer:embed` bundles src/renderer/embed.js, which installs a
browser bridge before app.js runs; every window.swaycommand call site works
unchanged. The embed differs from dist/ in four ways only: the entry point, a
<base href> for the host's mount path, a CSP without the Electron-only
frame-src gan:, and templates/docs copied in because there is no main process
to read them off disk.

The host channel (src/renderer/host/host-channel.js) relays MIDI bytes, audio
analysis and tab visibility over postMessage. Windows lets one process hold a
MIDI input, so embedded mode never opens the hardware itself; relayed bytes go
through the same decode path as the wire. Host analysis frames enter audio.js
as the raw read and fall through the same AGC, smoothing and beat detection,
decaying back to the local analyser after 500 ms of silence.

Three boot fixes found on the way:

- requestMIDIAccess() never settles until Chromium's permission prompt is
  answered. Awaiting it bare left the blast door locked with #boot-status
  frozen and nothing logged. Bounded to 3 s in midi.js, 6 s per check in
  runDoctor(), with a late grant still picked up by onstatechange.
- `available` reported false in relayed mode, so the splash said WebMIDI was
  unavailable while relayed MIDI was audibly playing.
- The AudioContext built at boot starts suspended under autoplay policy. The
  ENTER click resumes it; without that the analyser read silence and every
  scene rendered flat.

Plugin postMessage now checks e.source against the gan frame's contentWindow.
Embedded, this window has a parent and siblings that can also post to it.
The README now opens on a banner and a still from Miracle Mile, carries a
sixteen-scene gallery with a one-line mechanism note under each image, and
documents the parts that make the application an instrument rather than a
feature list: the cockpit as a line-art diagram plus a region table, the
recovered Sway factory map with its CC numbers, the target grammar every
assignable destination answers, and the timeline's track chains and sections.
Facts were re-checked against the source: sixteen scenes (five folded into the
scenes that own their subject last pass), eleven templates, 38 rack parameters
in five decks, 14 live track effect kinds, seven synth presets. The wormhole
row is gone from the controls table, since the wormhole is an element of Will I
Dream now and not a scene.

The images are generated, not captured. docs/media/gallery.plan.json is the
scene-harness plan behind every still, with the exact io snapshot each scene was
photographed under; docs/media/README.md carries the provenance table and the
regeneration command. Setup shots that only advance a scene into the state the
next shot photographs are prefixed with an underscore. Stills render at
1280x720 and are re-encoded to WebP at 480x270 through a canvas in the same
Electron runtime, which keeps the whole asset set at 472 KB.

Making that render in the in-app documentation viewer took three small changes.
markdown.js grows an image rule, placed ahead of the link rule that would
otherwise eat the bracket pair and leave a stray '!'; only the bundle's own
media directory resolves, so an external badge falls back to its alt text
rather than drawing a broken-image icon under `img-src 'self' data:`.
build-renderer.js copies docs/media/ to media/ beside index.html for both
targets, images only. Both title derivations (listDocs in main.js, the embed's
docs-index) fall back to a leading banner image's alt text, because the README
now has no H1: a wordmark image followed by the same word as a heading titles
the page twice.

LICENSE was missing while package.json, the README and the embed build all
declared MIT. It names Daniel Trujillo as the copyright holder and points at the
per-file upstream notices for the derived work.
Copilot AI lite review requested due to automatic review settings August 30, 2026 16:48
@danieljtrujillo
danieljtrujillo merged commit 01dbe30 into main Aug 30, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed correctness/documented-contract issues in the embedded MIDI permission flow and browser bridge error behavior that can leave features unavailable or crash the renderer.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces an embedded (non-Electron) build target for the SwayCommand renderer so the “cockpit” can run as a static bundle served by a host app (e.g., embedded in an iframe), with host integration for MIDI/audio/visibility plus documentation/media bundling.

Changes:

  • Added a browser/host bridge layer (window.swaycommand adapter + postMessage channel) so the renderer can run without Electron preload/IPCs.
  • Updated MIDI and audio analysis to support host-relayed input when embedded (and improved boot robustness around long-running checks).
  • Extended the Markdown/docs pipeline to support local images in the in-app docs viewer, and expanded build/docs assets (README refresh, media plan, dist-embed build output).
File summaries
File Description
src/renderer/styles.css Adds styling for Markdown-rendered doc images.
src/renderer/midi/midi.js Adds embedded (host-relayed) MIDI path and a bounded wait for WebMIDI permissions.
src/renderer/markdown.js Adds Markdown image rendering with local-only media rewriting.
src/renderer/host/host-channel.js Implements postMessage channel for host → cockpit MIDI/audio/visibility.
src/renderer/host/browser-bridge.js Implements a browser fallback for the Electron preload window.swaycommand surface.
src/renderer/host/bridge.js Installs the browser bridge + host channel when no native preload exists.
src/renderer/engine/audio.js Consumes host-provided analysis frames when available, falling back to local analyser.
src/renderer/embed.js New embedded entrypoint that installs the host bridge before app boot.
src/renderer/app.js Hardens Doctor checks with timeouts and tightens plugin-frame message source filtering; resumes AudioContext on user gesture.
src/main/main.js Allows docs title extraction from a leading banner image alt text.
scripts/build-renderer.js Adds dist-embed/ build mode, copies docs/templates/media, and stamps build provenance.
README.md Major rewrite with local media gallery and updated development/build instructions.
package.json Adds embed build script(s).
LICENSE Adds MIT license text plus third-party attribution note.
docs/media/README.md Documents provenance/regeneration of README media assets.
docs/media/gallery.plan.json Adds scene-harness plan used to generate README stills.
.gitignore Ignores dist-embed/ build output.
Review details

Suppressed comments (1)

src/renderer/host/browser-bridge.js:156

  • readDoc() throws on unknown ids and can throw on fetch failures, which conflicts with the module’s “NOTHING MAY REJECT” guarantee. Even though loadDoc() catches today, keeping the bridge non-throwing makes it safer to reuse elsewhere and aligns with the stated design constraint.
  • Files reviewed: 16/35 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/renderer/midi/midi.js
Comment on lines +31 to +43
// requestMIDIAccess does not settle until the user answers Chromium's
// permission prompt - measured hanging indefinitely when it is never
// answered. Awaiting it bare wedges main(), leaving the blast door
// locked with no error anywhere. Give it a bounded wait and carry on
// without MIDI if it does not arrive; a late grant is picked up by the
// statechange handler below.
access = await Promise.race([
navigator.requestMIDIAccess({ sysex: false }),
new Promise((resolve) => setTimeout(() => resolve(null), 3000)),
]);
if (!access) {
pushMonitor('MIDI permission not answered - continuing without it.');
}
Comment on lines +130 to +140
async function readTemplate(id) {
const order = await templateIndex();
if (!order.includes(id)) throw new Error(`Unknown template: ${id}`);
const res = await fetchStatic(`templates/${id}.sway`);
const raw = await res.json();
// validateProject lives in src/shared and is bundled; the host module imports
// it lazily to keep this file free of a hard dependency cycle.
const { validateProject } = await import('../../shared/swayproject.js');
const { doc, warnings } = validateProject(raw);
return { doc, path: null, dir: null, warnings };
}
Comment thread scripts/build-renderer.js
Comment on lines +95 to +99
// Every asset load is relative, so one <base> repoints the whole document at
// the host's mount path. Without it the AudioWorklet and the fonts resolve
// against the host's root and 404.
out = out.replace(/<head>/i, `<head>\n <base href="${BASE}" />`);

Comment thread package.json
Comment on lines +15 to +17
"dist:linux": "npm run build:icon && npm run build:renderer && electron-builder --linux",
"build:renderer:embed": "node scripts/build-renderer.js --embed --base=/sway-app/",
"build:all": "npm run build:renderer && npm run build:renderer:embed"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants